class MyEvent {
  mp = new Map();
  constructor() {}
  add(eventName, cb) {
    const cbs = this.mp.get(eventName);
    if (cbs) {
      cbs.push(cb);
    } else {
      this.mp.set(eventName, [cb]);
    }
  }

  remove(eventName, cb) {
    const cbs = this.mp.get(eventName);
    if (cbs) {
      this.mp.set(
        eventName,
        cbs.filter((item) => item !== cb)
      );
    }
  }

  emit(eventName, ...args) {
    const cbs = this.mp.get(eventName);
    cbs.forEach((cb) => {
      const r = cb.apply(cb, args);
      console.log(`${eventName}事件：`, "入参：", ...args, "结果：", r);
    });
  }

  once(eventName, cb) {
    const onceCb = (...args) => {
      const r = cb.apply(cb, args);
      this.remove(eventName, onceCb);
      return r;
    };

    this.add(eventName, onceCb);
  }
}

// ***************************** test ***************************************
function addNumber(a, b) {
  return a + b;
}

function addString(a, b) {
  return "hello: " + a + b;
}

// case1: add, remove
// const e1 = new MyEvent();

// e1.add("add", addNumber);
// e1.add("add", addString);
// e1.emit("add", 1, 2);
// e1.remove("add", addNumber);
// e1.emit("add", 1, 2);

// case2: once
const e1 = new MyEvent();
e1.once("xxx", addNumber);

e1.emit("xxx", 2, 3);

e1.emit("xxx", 2, 3);
